package com.iretailer.report.quartz.dailyReport;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.iretailer.report.influxdb.dao.InfluxdbDao;
import com.iretailer.report.influxdb.model.Passengerflow;
import com.iretailer.report.influxdb.util.TableName;
import com.iretailer.report.model.Categories;
import com.iretailer.report.model.MallBusinessTime;
import com.iretailer.report.model.Malls;
import com.iretailer.report.model.PhysicalZoneMall;
import com.iretailer.report.quartz.model.QuartTask;
import com.iretailer.report.service.*;
import com.iretailer.report.util.StringUtil;
import com.iretailer.report.util.TimeUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.mail.MessagingException;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @program: report
 * @description:
 * @author: Bennie
 * @create: 2020-08-10 10:36
 **/

@Slf4j
@Component("dailyReport")
@RequiredArgsConstructor
public class DailyReport {

    private final QuartTaskService quartTaskService;

    private final MallsService mallsService;

    private final BusinessTimeMallService businessTimeMallService;

    private final PhysicalzonesService physicalzonesService;

    private final CategoriesService categoriesService;

    private final InfluxdbDao influxdbDao;

    private final MailService mailService;

    @Transactional
    public void execute() {
        log.info("Executing task -------------> dailyReport");
        String beanName = this.getClass().getAnnotation(Component.class).value();
        if (StringUtil.empty(beanName)) {
            beanName = StringUtil.toLowerCaseFirstOne(this.getClass().getName());
        }
        QuartTask quartTask = quartTaskService.getOne(new QueryWrapper<QuartTask>().lambda().eq(QuartTask::getSpringId, beanName));
        if (quartTask == null) {
            log.error("未找到对应的 QuartzTask 【{}】", beanName);
            return;
        }
        String otherMessage = quartTask.getOtherMessage();
        if (StringUtil.empty(otherMessage)) {
            log.error("QuartzTask 【{}】参数为空", beanName);
        }
        Categories entrance = categoriesService.selectCategoryByKeyName("Entrance");
        if (entrance == null) {
            log.error("QuartzTask 【{}】主出入口 Categories 【Entrance】", beanName);
            return;
        }
        String[] mallNames = otherMessage.split(",");
        List<Malls> mallsList = new ArrayList<>();
        for (String mallName : mallNames) {
            Malls mall = mallsService.getOne(new QueryWrapper<Malls>().lambda().eq(Malls::getName, mallName).eq(Malls::getEnabled, 1));
            if (mall == null) {
                log.error("QuartzTask 【{}】未能找到对应的案场 【{}】", beanName, mallName);
                return;
            }
            mallsList.add(mall);
        }
        DateTimeFormatter tf = TimeUtil.DEFAULT_TIME_FORMATTER;
        Map<String, MallBusinessTime> mMap = mallsList.stream().map(mall -> {
            List<MallBusinessTime> ms = businessTimeMallService.selectMallBusinessTime(new QueryWrapper<>().eq("MallName", mall.getName()).eq("MallEnable", 1));
            if (CollectionUtils.isEmpty(ms)) {
                MallBusinessTime m = new MallBusinessTime();
                m.setStartTime(LocalTime.of(10, 0, 0).format(tf));
                m.setEndTime(LocalTime.of(22, 0, 0).format(tf));
                m.setMallId(mall.getId());
                m.setMallName(mall.getName());
                m.setWeekdays("Tue,Mon,Wed,Thu,Fri,Sat,Sun");
                return m;
            }
            return ms.get(0);
        }).collect(Collectors.toMap(MallBusinessTime::getMallName, a -> a));
        Map<String, List<PhysicalZoneMall>> pzMap = physicalzonesService.selectByMallIds_ZoneTypeId(mallsList.stream()
                .map(Malls::getId).collect(Collectors.toList()), entrance.getId()).stream()
                .collect(Collectors.groupingBy(PhysicalZoneMall::getMallName));
        LocalDateTime now = LocalDateTime.now();
        LocalTime localTime = now.toLocalTime();
        LocalDate localDate = now.toLocalDate();
        DayOfWeek dayOfWeek = now.getDayOfWeek();
        for (String key : mMap.keySet()) {
            List<PhysicalZoneMall> pms = pzMap.get(key);
            if (CollectionUtils.isEmpty(pms)) {
                log.warn("QuartzTask 【{}】未能找到对应案场的物理区域类型 【{}】", beanName, key);
                continue;
            }
            String principalMail = pms.get(0).getPrincipalMail();
            if (StringUtil.empty(principalMail)) {
                log.warn("QuartzTask 【{}】未能找到对应案场的负责人邮箱 【{}】", beanName, key);
                continue;
            }
            String emailRex = "\\w[-\\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\\.)+[A-Za-z]{2,14}";
            List<String> emails = Arrays.stream(principalMail.split(",")).filter(email -> {
                boolean matches = Pattern.matches(emailRex, email);
                if (!matches) {
                    log.warn("无效的邮箱地址 【{}】", email);
                }
                return matches;
            }).collect(Collectors.toList());
            if (CollectionUtils.isEmpty(emails)) {
                log.warn("QuartzTask 【{}】未能找到对应案场的负责人邮箱 【{}】", beanName, key);
                continue;
            }
            MallBusinessTime mbt = mMap.get(key);
            if (isBusinessDay(mbt.getWeekdays(), dayOfWeek)) {
                DateTimeFormatter dtf = TimeUtil.DEFAULT_DATE_TIME_FORMATTER;
                String st = mbt.getStartTime();
                LocalTime startTime = LocalTime.parse(StringUtil.empty(st) ? "10:00:00" : st, tf);
                String et = mbt.getEndTime();
                LocalTime endTime = LocalTime.parse(StringUtil.empty(et) ? "22:00:00" : et, tf);
                LocalDate ld = localTime.isAfter(endTime) ? localDate : localDate.minusDays(1);
                LocalDateTime startDateTime = LocalDateTime.of(ld, startTime);
                LocalDateTime endDateTime = LocalDateTime.of(ld, endTime);
                List<Long> zoneIds = pms.stream().map(PhysicalZoneMall::getId).collect(Collectors.toList());
                // 当天客流数据
                List<Passengerflow> d_pfs = passengerFlowList(mbt.getTimeZone(), zoneIds,
                        startDateTime.format(dtf),
                        endDateTime.format(dtf));
                // 当月客流数据
                List<Passengerflow> m_pfs = filterByBusiness(passengerFlowList(mbt.getTimeZone(), zoneIds,
                        getMothFirstDay(startDateTime).format(dtf), endDateTime.format(dtf)), startTime, endTime);
                // 去年同月客流数据
                List<Passengerflow> lym_pfs = filterByBusiness(passengerFlowList(mbt.getTimeZone(), zoneIds,
                        getMothFirstDay(startDateTime.minusYears(1)).format(dtf),
                        getMothLastDay(endDateTime.minusYears(1)).format(dtf)), startTime, endTime);
                // 去年同天客流数据
                List<Passengerflow> lyd_pfs = passengerFlowList(mbt.getTimeZone(), zoneIds,
                        startDateTime.minusYears(1).format(dtf),
                        endDateTime.minusYears(1).format(dtf));
                // 本周客流数据
                List<Passengerflow> w_pfs = filterByBusiness(passengerFlowList(mbt.getTimeZone(), zoneIds,
                        getWeekFirstDay(startDateTime).format(dtf), endDateTime.format(dtf)), startTime, endTime);
                // 上周客流数据
                List<Passengerflow> lw_pfs = filterByBusiness(passengerFlowList(mbt.getTimeZone(), zoneIds,
                        getWeekFirstDay(startDateTime.minusWeeks(1)).format(dtf),
                        getWeekLastDay(endDateTime.minusWeeks(1)).format(dtf)), startTime, endTime);

                List<List<Object>> data = new ArrayList<>();
                data.add(Arrays.asList(
                        "案场名称", "今日客流", "同比去年", "环比上周", "本月客流", "本月同比去年"
                ));
                // 当日客流量
                int df = countFlow(d_pfs);
                // 去年当日客流
                int ly_df = countFlow(lyd_pfs);
                // 同比
                double dur = df == 0 ? 0 : ly_df ==0 ? 100 :((df - ly_df) / (double) ly_df * 100.0);
                // 本月客流量
                int mf = countFlow(m_pfs);
                // 去年同月客流量
                int ly_mf = countFlow(lym_pfs);
                // 同比
                double mur = mf == 0 ? 0 :ly_mf==0? 100:  ((mf - ly_mf) / (double) ly_mf * 100.0);
                data.add(Arrays.asList(
                        mbt.getMallName(), df,
                        String.format("<p>%.2f%%</p><img src='%s' style='width:20px;height:20px;'/><p>%s</p>",
                                Math.abs(dur), dur > 0 ? UP_IMG : DOWN_IMG, df
                        ),
                        String.format("<p>%s</p><p>%s</p>", countFlow(lw_pfs), countFlow(w_pfs)), mf,
                        String.format("<p>%.2f%%</p><img src='%s' style='width:20px;height:20px;'/><p>%s</p>",
                                Math.abs(mur), mur > 0 ? UP_IMG : DOWN_IMG, mf
                        )
                ));
                try {
                    mailService.send(emails.toArray(new String[0]), String.format("【%s】每日客流报表", mbt.getMallName()),
                            new HashMap<String, Object>() {
                                {
                                    put("data", data);
                                    put("date", ld.format(TimeUtil.DEFAULT_DATE_FORMATTER));
                                    put("mallName", mbt.getMallName());
                                }
                            }, "daily-report");
                } catch (MessagingException e) {
                    log.error("发送邮件失败,【{}】", String.join(",", emails));
                }
            }
        }

    }


    private Integer countFlow(List<Passengerflow> pfs) {
        if (CollectionUtils.isEmpty(pfs)) return 0;
        return pfs.stream().map(Passengerflow::getCountEnter).reduce(Integer::sum).orElse(0);
    }

    private LocalDateTime getWeekFirstDay(LocalDateTime ldt) {
        LocalDateTime wd = ldt.minusSeconds(ldt.getDayOfWeek().getValue() - 1);
        return LocalDateTime.of(wd.getYear(), wd.getMonth(), wd.getDayOfMonth(), 0, 0, 0, 0);
    }

    private LocalDateTime getWeekLastDay(LocalDateTime ldt) {
        return getWeekFirstDay(ldt.plusWeeks(1)).minusNanos(1);
    }

    private LocalDateTime getMothFirstDay(LocalDateTime ldt) {
        return LocalDateTime.of(ldt.getYear(), ldt.getMonth(), 1, 0, 0, 0, 0);
    }

    private LocalDateTime getMothLastDay(LocalDateTime ldt) {
        return getMothFirstDay(ldt.plusMonths(1)).minusNanos(1);
    }

    private List<Passengerflow> passengerFlowList(String timeZone, List<Long> zoneIds, String st, String et) {
        return influxdbDao.selectflowGroupByZoneId(
                true, getString(timeZone, "Asia/Shanghai"),
                TableName.Passengerflow.Passengerflow_PZ, zoneIds,
                st, et, "60m");
    }

    private List<Passengerflow> filterByBusiness(List<Passengerflow> pfs, LocalTime st, LocalTime et) {
        return pfs.stream().filter(pf -> {
            LocalTime lt = pf.getTime().toLocalDateTime().toLocalTime();
            LocalTime pn = et.plusNanos(1);
            boolean zeroDt = st.equals(LocalTime.parse("00:00", DateTimeFormatter.ofPattern("HH:mm")));
            return lt.isBefore(pn) && (zeroDt || lt.isAfter(st.minusNanos(1)));
        }).collect(Collectors.toList());
    }


    private String getString(String s1, String s2) {
        return StringUtil.empty(s1) ? s2 : s1;
    }

    private boolean isBusinessDay(String businessWeekday, DayOfWeek dayOfWeek) {
        if (StringUtil.empty(businessWeekday)) {
            return false;
        }
        int v = dayOfWeek.getValue();
        String weekStr = v == 1 ? "Mon" : v == 2 ? "Tue" : v == 3 ? "Wed" : v == 4 ? "Thu" : v == 5 ? "Fri" : v == 6 ? "Sat" : "Sun";
        String[] weeks = businessWeekday.split(",");
        for (String week : weeks) {
            if (weekStr.toLowerCase().equals(week.toLowerCase())) {
                return true;
            }
        }
        return false;
    }

    public static final String UP_IMG = "";

    public static final String DOWN_IMG = "";
}